#include "../SocketsOps.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>   // snprintf
#include <strings.h> // bzero
#include <sys/socket.h>
#include <unistd.h>

using namespace yhaida;
namespace
{
    typedef struct sockaddr SA;
    // sockaddr_in -> sockaddr
    const SA *sockaddr_cast(const struct sockaddr_in *addr)
    {
        return static_cast<const SA *>(yhaida::Utils::implicit_cast<const void *>(addr));
    }
    SA *sockaddr_cast(struct sockaddr_in *addr)
    {
        return static_cast<SA *>(yhaida::Utils::implicit_cast<void *>(addr));
    }

    //设置sockfd的一些属性
    // void setNonBlockAndCloseOnExec(int sockfd)
    // {
    //     int flags = ::fcntl(sockfd, F_GETFL, 0);
    //     flags |= O_NONBLOCK;
    //     ::fcntl(sockfd, F_SETFL, flags);

    //     flags = ::fcntl(sockfd, F_GETFD, 0);
    //     flags |= FD_CLOEXEC;
    //     ::fcntl(sockfd, F_SETFD, flags);
    //     // 这里设置为FD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递给exec创建的新进程, 如果设置为fcntl(fd, F_SETFD, 0);那么本fd将保持打开状态复制到exec创建的新进程中
    //     // FD_CLOEXEC用来设置文件的close-on-exec状态标准。在exec()调用后，close-on-exec标志为0的情况，此文件不被关闭。非零则在exec()后被关闭。默认close-on-exec状态为0，需要通过FD_CLOEXEC设置。
    // }
}

// 1.创建
int sockets::createNonblockingOrDie()
{
    // AF_INET（TCP/IP – IPv4）
    // #if VALGRIND
    // int sockfd = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    // if(sockfd<0)
    // {
    //     LOG_SYSFATAL<<"sockets::createNonblockingOrDie";
    // }
    // //设置
    // setNonBlockAndCloseOnExec(sockfd);
    int sockfd = ::socket(AF_INET, SOCK_STREAM | O_NONBLOCK | FD_CLOEXEC, IPPROTO_TCP);
    if (sockfd < 0)
    {
        LOG_SYSFATAL << "sockets::createNonblockingOrDie";
    }
    return sockfd;
}
// 2.连接
void sockets::bindOrDie(int sockfd, const struct sockaddr_in &addr)
{
    int ret = ::bind(sockfd, sockaddr_cast(&addr), sizeof(addr));
    if (ret < 0)
    {
        LOG_SYSFATAL << "sockets::bindOrDie";
    }
}
// 3.监听
void sockets::listenOrDie(int sockfd)
{
    int ret = ::listen(sockfd, SOMAXCONN);
    if (ret < 0)
    {
        LOG_SYSFATAL << "sockets::listenOrDie";
    }
}
// 4.accept
int sockets::accept(int sockfd, struct sockaddr_in *addr)
{
    socklen_t addrlen = sizeof(*addr);
    // #if VALGRIND
    // int connfd = ::accept(AF_INET,sockaddr_cast(addr) , &addrlen);
    // setNonBlockAndCloseOnExec(connfd);

    int connfd = ::accept4(sockfd, sockaddr_cast(addr), &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

    if (connfd < 0)
    {
        int savedErrno = errno;
        LOG_SYSERR << "Socket::accept";
        switch (savedErrno)
        {
        case EAGAIN:
        case ECONNABORTED:
        case EINTR:
        case EPROTO: // ???
        case EPERM:
        case EMFILE: // per-process lmit of open file desctiptor ???
            // expected errors
            errno = savedErrno;
            break;
        case EBADF:
        case EFAULT:
        case EINVAL:
        case ENFILE:
        case ENOBUFS:
        case ENOMEM:
        case ENOTSOCK:
        case EOPNOTSUPP:
            // unexpected errors
            LOG_FATAL << "unexpected error of ::accept " << savedErrno;
            break;
        default:
            LOG_FATAL << "unknown error of ::accept " << savedErrno;
            break;
        }
    }

    return connfd;
}
// 5.关闭
void sockets::close(int sockfd)
{
    if (::close(sockfd) < 0)
    {
        LOG_SYSERR << "sockets::close";
    }
}
/*
调用 close()/closesocket() 关闭套接字时，
或调用 shutdown() 关闭输出流时，都会向对方发送 FIN 包。
FIN 包表示数据传输完毕，计算机收到 FIN 包就知道不会再有数据传送过来了。

默认情况下，close()/closesocket() 会立即向网络中发送FIN包，
不管输出缓冲区中是否还有数据，
而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。
也就意味着，调用 close()/closesocket() 将丢失输出缓冲区中的数据，
而调用 shutdown() 不会。
*/
void sockets::shutdownWrite(int sockfd)
{
    if (::shutdown(sockfd, SHUT_WR) < 0)
    {
        LOG_SYSERR << "sockets::shutdownWrite";
    }
}

//目的IP地址与端口 accept过来的IP地址与端口 把addr->写入buff（"%s:%u"）例如:  "192.168.0.1:8808"
void sockets::toHostPort(char *buff, size_t size, const struct sockaddr_in &addr)
{
    char host[INET_ADDRSTRLEN] = "INVALID";
    ::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof(host));

    uint16_t port = sockets::networkToHost16(addr.sin_port);
    snprintf(buff, size, "%s:%u", host, port);
}
//源IP地址与端口  字符串ip与port->addr
void sockets::fromHostPort(const char *ip, uint16_t port, struct sockaddr_in *addr)
{
    addr->sin_family = AF_INET;
    addr->sin_port = hostToNetwork16(port);
    if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
    {
        LOG_SYSERR << "sockets::fromHostPort";
    }
}

struct sockaddr_in sockets::getLocalAddr(int sockfd)
{
    struct sockaddr_in localaddr;
    bzero(&localaddr, sizeof localaddr);
    socklen_t addrlen = sizeof(localaddr);
    if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
    {
        LOG_SYSERR << "sockets::getLocalAddr";
    }
    return localaddr;
}

int sockets::getSocketError(int sockfd)
{
    int optval;
    socklen_t optlen = sizeof(optval);
    if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
    {
        return errno;
    }
    else
    {
        return optval;
    }
}

//客户端
int sockets::connect(int sockfd, const struct sockaddr_in &addr)
{
    return ::connect(sockfd, sockaddr_cast(&addr), sizeof(addr));
}
struct sockaddr_in sockets::getPeerAddr(int sockfd)
{
    struct sockaddr_in peeraddr;
    bzero(&peeraddr, sizeof peeraddr);
    socklen_t addrlen = sizeof(peeraddr);
    if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0)
    {
        LOG_SYSERR << "sockets::getPeerAddr";
    }
    return peeraddr;
}
bool sockets::isSelfConnect(int sockfd)
{
    struct sockaddr_in localaddr = getLocalAddr(sockfd);
    struct sockaddr_in peeraddr = getPeerAddr(sockfd);
    return localaddr.sin_port == peeraddr.sin_port && localaddr.sin_addr.s_addr == peeraddr.sin_addr.s_addr;
}